package com.hsmpay.mobile.util.jiamiji;

import org.apache.log4j.Logger;

public class HsmEngine{

	private static final Logger log = Logger.getLogger(HsmEngine.class);

	private int MAX_INDEX = 2048;
    private int KEYLEN_SINGLE = 8;
    
    private String DES_SCHEME	= "Z";  // 单长度DES密钥
    private String DES2_SCHEME	= "X"; 	// 双长度DES密钥
    private String DES2V_SCHEME	= "U";  // 双长度变种加密DES密钥
    private String DES3_SCHEME	= "Y";  // 三长度长度DES密钥
    private String DES3V_SCHEME	= "T";  // 三长度长度变种加密DES密钥
    private String SM1_SCHEME	= "P"; 	// SM1密钥
    private String SM4_SCHEME	= "R"; 	// SM4密钥
    private String AES_SCHEME	= "L"; 	// AES密钥

    HsmSocket hsmSock = null;

	public HsmEngine(String ip, int port) {
		hsmSock = new HsmSocket(ip, port);
	}

	/**
	 * 在密码机中产生随机对称密钥，保存的指定位置，并使用密码机种LMK加密导出。
	 * @return LMK加密的对称密钥及密钥校验值。【KS+2N错误码+16H/1A+32H/1A+48H密钥密文+16H/32H密钥校验值】
	 * 		   当密钥方案为Z/X/U/T/Y时密钥校验值为16H，当密钥方案为R/P/L时密钥校验值为16H。
	 * @throws Exception
	 */
    public String createWorkKey(int intTMKIndex, int intWorkKeyLen, String keytype, String keyScheme, String lable) throws Exception {
        StringBuffer sb = new StringBuffer();
        
        if ( keyScheme == null || lable == null || keytype == null || keytype.length() != 3 ) 
           	throw new Exception("Paramter is error.");
        
        if ( lable.length() > 16 ) 
        	throw new Exception("Key label length invalid. it is must be 0-16.");
        
        if ( MAX_INDEX < intTMKIndex || 1 > intTMKIndex )
        	throw new Exception("Work index invalid. It is must be 1-2048");
        
        if ( keyScheme.equals(DES_SCHEME) ) { // 单长度DES密钥方案，密钥长度必须为8.
        	if ( intWorkKeyLen != KEYLEN_SINGLE )
            	throw new Exception("Key length invalid. if keytype is Z it is must be 8.");
        } else if ( keyScheme.equals(DES2_SCHEME) || keyScheme.equals(DES2V_SCHEME) ||
        		keyScheme.equals(SM1_SCHEME) || keyScheme.equals(SM4_SCHEME) || 
        		keyScheme.equals(AES_SCHEME) ) { // 双长度DES密钥方案， SM4、SM1、AESM密钥方案，密钥长度必须为16.
        	if ( intWorkKeyLen != 2*KEYLEN_SINGLE )
            	throw new Exception("Key length invalid. if keytype is X U R P L & it is must be 16.");     	
        	
        } else if ( keyScheme.equals(DES3_SCHEME) || keyScheme.equals(DES3V_SCHEME) ) {  // 三长度DES密钥方案， 密钥长度必须为24
        	throw new Exception("Key length invalid. if keytype is Y T & it is must be 24.");     	    	
        } else {
        	throw new Exception("Key keytype invalid. it must be Z X U Y T R P L."); 
        }

		//封装发送报文
        sb.append("KR");  						// [2A] 指令码
        sb.append(keytype); 					// [3H] 密钥类型
        sb.append(keyScheme); 					// [1A] 密钥方案
        sb.append("K");							// [1A] 密钥索引标识
        sb.append(format(intTMKIndex, 4)); 		// [4N] 索引
        sb.append(format(lable.length(), 2)); 	// [2N] 标签长度
        sb.append(lable);						// [0-16A] 密钥标签
        
        byte[] cmdbuf = sb.toString().getBytes();
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
        return new String(rspbuf);
    }

	/**
	 * 使用LMK加密一个明文pin码 (BA命令)
	 * @return
	 */
	public String getLMKencryptPin(String pin,String accountNum){
		String accountNumber = accountNum.substring(accountNum.length()-13, accountNum.length()-1);
		StringBuffer sb = new StringBuffer("");
		sb.append("BA");
		sb.append(pin + "F");
		sb.append(accountNumber);
		byte[] cmdbuf = sb.toString().getBytes();
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		String result = new String(rspbuf);
		if(!result.substring(0,4).equals("BB00")){// BB00 成功响应  其余代表失败
			log.debug("加密机使用LMK加密明文PIN未收到正确响应!");
			throw new RuntimeException();
		}
		return result.substring(4,result.length());
	}

	/**
	 * 将pin由LMK加密转换为ZPK加密 (JG命令)
	 * @return
	 */
	public String encryptPin(String pin,String accountNum,String LPK){
		String LMKencryptPin = getLMKencryptPin(pin,accountNum);
		StringBuffer sb = new StringBuffer("");
		sb.append("JG");
		sb.append("X" + LPK);
		sb.append("11");//pinBlock格式
		sb.append(accountNum.substring(accountNum.length()-13, accountNum.length()-1));//用户主账号
		sb.append(LMKencryptPin);
		byte[] cmdbuf = sb.toString().getBytes();
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		String result = new String(rspbuf);
		if(!result.substring(0,4).equals("JH00")){// JH00 成功响应 其余代表失败
			log.debug("加密机将pin由LMK加密转换为ZPK加密未收到正确响应!");
			throw new RuntimeException();
		}
		return result.substring(4,result.length());
	}

	/**
	 * 格式化字符串
	 * @param msglen
	 * @param formatLen
	 * @return
     */
	private String format(int msglen, int formatLen) {
        String sFormatlen = "" + msglen;
        int iFormatlen = sFormatlen.length();
        for (int i = 0; i < formatLen - iFormatlen; i++) {
            sFormatlen = "0" + sFormatlen;
        }
        return sFormatlen;
    }

	/**
	 * 磁道加密 (D3命令)
	 * @return
	 */
	public String encryptTrackD3(String data,String key){
		StringBuffer sb = new StringBuffer("");
		sb.append("D3");//命令代码
		sb.append("00");//加密算法模式
		sb.append("00A");//秘钥类型
		sb.append("X" + key);//用于加密的秘钥索引或密文
		sb.append("00");//秘钥分散级数
		sb.append("00");//会话秘钥模式
		sb.append("02");//PAD标识
		String length = "00"+Integer.toHexString(data.getBytes().length/2).toUpperCase();
		sb.append(length);//输入数据长度
		sb.append(data);//输入数据

		byte[] cmdbuf = sb.toString().getBytes();
		//发送数据到密码机并接收
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		String result = new String(rspbuf);
		if(!result.substring(0,4).equals("D400")){// D400 成功响应 其余代表失败
			log.debug("加密机磁道加密(D3命令)未收到正确响应!");
			throw new RuntimeException();
		}
		return result.substring(8,result.length());
	}

	/**
	 * mac加密(MS命令)
	 * @return
	 */
	public String encryptMacMS(String data,String LAK) throws Exception {
		StringBuffer sb = new StringBuffer("");
		sb.append("MS");//命令代码
		sb.append("0");//报文块标识
		sb.append("1");//密钥类型
		sb.append("1");//密钥长度
		sb.append("1");//数据类型
		sb.append("X" + LAK);//用于加密的秘钥索引或密文
		String length = "00" + Integer.toHexString(data.getBytes().length/2);
		sb.append(length);//输入数据长度
		sb.append(data);//输入数据
		byte[] cmdbuf = sb.toString().getBytes();
		//发送数据到密码机并接收
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		if(!new String(rspbuf).substring(0,4).equals("MT00")){
			log.debug("加密机计算MAC(MS命令)未收到正确响应!");
			throw new RuntimeException();
		}
		byte[] result = new byte[rspbuf.length - 4];
		System.arraycopy(rspbuf, 4, result, 0, rspbuf.length - 4);
		byte[] macByte = byte2hex(hexStringToByte(new String(result))).substring(0,8).getBytes();
		String mac = byte2hex(macByte);
		return mac;
	}

	/**
	 * 获取密码机版本信息。
	 * @param
	 * @return 密码机版本信息。【ND+2N错误码+16H密码机主密钥LMK密钥校验值+24A的密码机版本号】
	 * @throws Exception
	 */
	public String getHsm_version() throws Exception {
		StringBuffer sb = new StringBuffer();
		sb.append("NC"); //2N 指令码
		byte[] cmdbuf = sb.toString().getBytes();
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		return new String(rspbuf);
	}

	/**
	 * ZPK由ZMK加密转为 LMK加密
	 * @param index 通道方主密钥在加密机里的位置索引
	 * @param ZPK 签到回来的PIN密钥
	 * @return 加密机转加密后的LPK
	 * @throws Exception
	 */
	public String transZPK(int index,String ZPK) throws Exception{
		StringBuffer sb = new StringBuffer("");
		//封装发送报文
		sb.append("A6");  						// [2A] 指令码
		sb.append("001"); 					    // [3H] 密钥类型
		sb.append("K" + format(index, 4));          // [4N] zmk密钥索引
		sb.append("X" + ZPK);
		sb.append("X");							// [1A] 密钥标识
		byte[] cmdbuf = sb.toString().getBytes();
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		if(!new String(rspbuf).substring(0,4).equals("A700")){
			log.debug("加密机转加密ZPK失败!");
			throw new RuntimeException();
		}
		byte[] result = new byte[rspbuf.length-21];
		System.arraycopy(rspbuf,5,result,0,rspbuf.length-21);
		return new String(result);
	}
	/**
	 * 转加密ZAK
	 * @param index 通道方主密钥在加密机里的位置索引
	 * @param ZAK 签到回来的MAC密钥
	 * @return 加密机转加密后的LAK
	 * @throws Exception
	 */
	public String transZAK(int index,String ZAK) throws Exception{
		StringBuffer sb = new StringBuffer("");
		//封装发送报文
		sb.append("A6");  						// [2A] 指令码
		sb.append("008"); 					    // [3H] 密钥类型

		sb.append("K" + format(index, 4));          // [4N] zmk密钥索引
		sb.append("X" + ZAK);
		sb.append("X");							// [1A] 密钥标识
		byte[] cmdbuf = sb.toString().getBytes();

		//发送数据到密码机并接收返回结果
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		if(!new String(rspbuf).substring(0,4).equals("A700")){
			log.debug("加密机转加密ZAK失败!");
			throw new RuntimeException();
		}
		byte[] result = new byte[rspbuf.length-21];
		System.arraycopy(rspbuf,5,result,0,rspbuf.length-21);
		return new String(result);
	}

	/**
	 * 转加密ZEK
	 * @param index 通道方主密钥在加密机里的位置索引
	 * @param ZEK 签到回来的TRACK密钥
	 * @return 加密机转加密后的LEK
	 * @throws Exception
	 */
	public String transZEK(int index,String ZEK) throws Exception{
		StringBuffer sb = new StringBuffer("");
		//封装发送报文
		sb.append("A6");  						// [2A] 指令码
		sb.append("00A"); 					    // [3H] 密钥类型

		sb.append("K" + format(index, 4));          // [4N] zmk密钥索引
		sb.append("X" + ZEK);
		sb.append("X");							// [1A] 密钥标识
		byte[] cmdbuf = sb.toString().getBytes();
		//发送数据到密码机并接收返回结果
		byte[] rspbuf = hsmSock.CommunicateHsm(cmdbuf);
		if(!new String(rspbuf).substring(0,4).equals("A700")){
			log.debug("加密机转加密ZEK失败!");
			throw new RuntimeException();
		}
		byte[] result = new byte[rspbuf.length-21];
		System.arraycopy(rspbuf,5,result,0,rspbuf.length-21);
		return new String(result);
	}

	public static byte[] hexStringToByte(String hex) {
		hex=hex.toUpperCase();
		int len = (hex.length() / 2);
		byte[] result = new byte[len];
		char[] achar = hex.toCharArray();
		for (int i = 0; i < len; i++) {
			int pos = i * 2;
			result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
		}
		return result;
	}

	private static byte toByte(char c) {
		byte b = (byte) "0123456789ABCDEF".indexOf(c);
		return b;
	}

	public static String byte2hex(byte[] b) {
		String hs = "";
		String stmp = "";
		for (int n = 0; n < b.length; n++) {
			stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
			if (stmp.length() == 1)
				hs = hs + "0" + stmp;
			else
				hs = hs + stmp;
		}
		return hs.toUpperCase();
	}

}
